﻿/**
 * Stardust Particle Engine
 * 
 * Homepage
 * 	http://code.google.com/p/stardust-particle-engine/
 * 
 * Documentation
 * 	http://stardust-particle-engine.googlecode.com/svn/trunk/docs/index.html
 * 
 * PDF Manual
 * 	http://stardust-particle-engine.googlecode.com/svn/trunk/manual/Stardust%20Particle%20Engine%20Manual.pdf
 */

package {
	import com.bit101.components.PushButton;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BlendMode;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.filters.BlurFilter;
	import flash.filters.GlowFilter;
	import flash.geom.ColorTransform;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.utils.Timer;
	import idv.cjcat.stardust.common.clocks.ImpulseClock;

	[SWF(backgroundColor=0x000000, frameRate=60)]
	
	public class Main extends Sprite {
		
		private var timer:Timer = new Timer(1500);
		
		private var clock:ImpulseClock = new ImpulseClock(1);
		private var emitter:FireworksEmitter;
		private var renderer:LineRenderer;
		
		private var canvas:BitmapData = new BitmapData(465, 465, true, 0);
		private var fader:BitmapData = new BitmapData(465, 465, false, 0);
		private var glow:BitmapData = new BitmapData(233, 233, false, 0);
		
		public function Main() {
			//timer
			timer.addEventListener(TimerEvent.TIMER, tick);
			timer.start();
			
			//canvas
			addChild(new Bitmap(fader));
			addChild(new Bitmap(canvas));
			var glowBMP:Bitmap = new Bitmap(glow);
			glowBMP.blendMode = BlendMode.ADD;
			glowBMP.scaleX = glowBMP.scaleY = 2;
			addChild(glowBMP);
			
			//Stardust engine
			emitter = new FireworksEmitter(clock);
			renderer = new LineRenderer(canvas);
			renderer.addEmitter(emitter);
			
			//listener
			addEventListener(Event.ENTER_FRAME, mainLoop);
			tick();
			
			//speed buttons
			var btn0:PushButton = new PushButton(this, 10, 10, "0X", changeSpeed);
			var btn1:PushButton = new PushButton(this, 10, 30, "0.25X", changeSpeed);
			var btn2:PushButton = new PushButton(this, 10, 50, "0.5X", changeSpeed);
			var btn3:PushButton = new PushButton(this, 10, 70, "1X", changeSpeed);
			var btn4:PushButton = new PushButton(this, 10, 90, "2X", changeSpeed);
			var btn5:PushButton = new PushButton(this, 10, 110, "4X", changeSpeed);
			btn0.width = 
			btn1.width =
			btn2.width =
			btn3.width =
			btn4.width =
			btn5.width = 50;
		}
		
		//main loop
		private var matrix:Matrix = new Matrix(0.5, 0, 0, 0.5);
		private var point:Point = new Point(0, 0);
		private var blur:BlurFilter = new BlurFilter(8, 8, 2);
		private var darken:ColorTransform = new ColorTransform(1, 1, 1, 0.95);
		private var lighten:ColorTransform = new ColorTransform(4, 4, 4);
		private function mainLoop(e:Event):void {
			emitter.step();
			
			fader.colorTransform(fader.rect, darken);
			fader.draw(canvas);
			glow.draw(fader, matrix);
			glow.applyFilter(glow, glow.rect, point, blur);
			glow.colorTransform(glow.rect, lighten);
			canvas.fillRect(canvas.rect, 0);
		}
		
		//shoots out a seed
		private function tick(e:TimerEvent = null):void {
			clock.impulse();
		}
		
		//changes playback speed
		private function changeSpeed(e:Event):void {
			var btn:PushButton = e.target as PushButton;
			
			if (btn.label == "0X") {
				emitter.actions.active = false;
				emitter.active = false;
				timer.stop();
				darken.alphaMultiplier = 1;
				return;
			}
			
			timer.start();
			emitter.actions.active = true;
			emitter.active = true;
			
			var dt:Number;
			switch (btn.label) {
				case "0.25X":	dt = 0.25;	break;
				case "0.5X":	dt = 0.5;	break;
				case "1X":		dt = 1;		break;
				case "2X":		dt = 2;		break;
				case "4X":		dt = 4;		break;
			}
			
			emitter.stepTimeInterval = dt;
			timer.delay = 1500 / dt;
			darken.alphaMultiplier = Math.pow(0.95, dt);
		}
	}
}

//------------------------------------------------------------------------------------------------

import flash.display.BitmapData;
import flash.display.Shape;
import flash.events.Event;
import idv.cjcat.stardust.common.actions.Action;
import idv.cjcat.stardust.common.actions.Age;
import idv.cjcat.stardust.common.actions.AlphaCurve;
import idv.cjcat.stardust.common.actions.CompositeAction;
import idv.cjcat.stardust.common.actions.DeathLife;
import idv.cjcat.stardust.common.actions.ScaleCurve;
import idv.cjcat.stardust.common.actions.triggers.DeathTrigger;
import idv.cjcat.stardust.common.clocks.Clock;
import idv.cjcat.stardust.common.emitters.Emitter;
import idv.cjcat.stardust.common.events.EmitterEvent;
import idv.cjcat.stardust.common.initializers.Alpha;
import idv.cjcat.stardust.common.initializers.CollisionRadius;
import idv.cjcat.stardust.common.initializers.Color;
import idv.cjcat.stardust.common.initializers.CompositeInitializer;
import idv.cjcat.stardust.common.initializers.Life;
import idv.cjcat.stardust.common.initializers.Mask;
import idv.cjcat.stardust.common.initializers.Mass;
import idv.cjcat.stardust.common.math.Random;
import idv.cjcat.stardust.common.math.StardustMath;
import idv.cjcat.stardust.common.math.UniformRandom;
import idv.cjcat.stardust.common.particles.Particle;
import idv.cjcat.stardust.common.renderers.Renderer;
import idv.cjcat.stardust.twoD.actions.Damping;
import idv.cjcat.stardust.twoD.actions.Gravity;
import idv.cjcat.stardust.twoD.actions.Move;
import idv.cjcat.stardust.twoD.actions.Spawn;
import idv.cjcat.stardust.twoD.actions.StardustSpriteUpdate;
import idv.cjcat.stardust.twoD.display.StardustSprite;
import idv.cjcat.stardust.twoD.emitters.Emitter2D;
import idv.cjcat.stardust.twoD.fields.UniformField;
import idv.cjcat.stardust.twoD.geom.MotionData2D;
import idv.cjcat.stardust.twoD.initializers.DisplayObjectClass;
import idv.cjcat.stardust.twoD.initializers.Position;
import idv.cjcat.stardust.twoD.initializers.Velocity;
import idv.cjcat.stardust.twoD.particles.Particle2D;
import idv.cjcat.stardust.twoD.zones.LazySectorZone;
import idv.cjcat.stardust.twoD.zones.SectorZone;
import idv.cjcat.stardust.twoD.zones.SinglePoint;

//------------------------------------------------------------------------------------------------

const COMMON_MASK		:int	= 1;
const SEED_MASK			:int	= 2;
const STREAK_MASK			:int	= 4;
const FIREWORKS_MASK	:int	= 8;
const SPARK_MASK		:int	= 16;

const SEED_ANGLE		:Number = 10;
const SEED_LIFE			:Number	= 125;
const SEED_SPEED_1		:Number = 3;
const SEED_SPEED_2		:Number = 4.5;
const SEED_COLOR		:uint	= 0xFFFFFF;
const SEED_RADIUS		:Number = 3;
const SEED_MASS			:Number = 1;

const STREAK_COUNT		:int = 15;
const STREAK_LIFE			:Number = 10;
const STREAK_SPEED		:Number = 4;
const STREAK_SPEED_V		:Number = 4;		//variation
const STREAK_COLOR		:Number = 0x9FFFFF;
const STREAK_RADIUS		:Number = 2;
const STREAK_MASS			:Number = 10;

const FIREWORKS_COUNT	:Number = 12;
const FIREWORKS_SPEED	:Number = 1.5;
const FIREWORKS_SPEED_V	:Number = 1.5;		//variation
const FIREWORKS_LIFE	:Number	= 70;
const FIREWORKS_LIFE_V	:Number	= 20;		//variation
const FIREWORKS_COLOR_1	:uint 	= 0x4466AA;
const FIREWORKS_COLOR_2	:uint	= 0x9966FF;
const FIREWORKS_DAMPING	:Number = 0.01;
const FIREWORKS_RADIUS	:Number = 3;
const FIREWORKS_MASS	:Number	= 2;

const SPARK_COUNT		:Number = 5;
const SPARK_SPEED		:Number = 1;
const SPARK_SPEED_V		:Number = 1;		//variation
const SPARK_LIFE		:Number = 30;
const SPARK_LIFE_V		:Number = 10;		//variation
const SPARK_COLOR_1		:uint	= 0xFF8033;
const SPARK_COLOR_2		:uint	= 0x993311;
const SPARK_DAMPING		:Number = 0.035;
const SPARK_RADIUS		:Number = 3;
const SPARK_MASS		:Number = 1;

//------------------------------------------------------------------------------------------------

class FireworksEmitter extends Emitter2D {
	
	public var actions:CompositeAction;
	
	public function FireworksEmitter(clock:Clock) {
		super(clock);
		
		//initializers
		addInitializer(new SeedInitializers());
		
		//actions
		actions = new CompositeAction();
		actions.checkComponentMasks = true;
		actions.addAction(new CommonActions());
		actions.addAction(new SeedActions());
		actions.addAction(new StreakActions());
		actions.addAction(new FireworksActions());
		actions.addAction(new SparkActions());
		addAction(actions);
	}
}

//------------------------------------------------------------------------------------------------

class SeedInitializers extends CompositeInitializer {
	
	public function SeedInitializers() {
		addInitializer(new Mask(SEED_MASK | COMMON_MASK));
		
		var sector:SectorZone = new SectorZone();
		sector.minRadius = SEED_SPEED_1;
		sector.maxRadius = SEED_SPEED_2;
		sector.minAngle = -SEED_ANGLE - 90;
		sector.maxAngle = SEED_ANGLE - 90;
		
		addInitializer(new Life(new UniformRandom(SEED_LIFE, 0)));
		addInitializer(new Position(new SinglePoint(232.5, 465)));
		addInitializer(new Velocity(sector));
		addInitializer(new Color(SEED_COLOR));
		addInitializer(new CollisionRadius(SEED_RADIUS));
		addInitializer(new Mass(new UniformRandom(SEED_MASS, 0)));
	}
}

//------------------------------------------------------------------------------------------------

class StreakSpawn extends Spawn {
	
	public function StreakSpawn(count:Random) {
		super(count);
		
		addInitializer(new Mask(STREAK_MASK | COMMON_MASK));
		
		addInitializer(new Color(STREAK_COLOR));
		addInitializer(new Life(new UniformRandom(STREAK_LIFE, 0)));
		addInitializer(new Velocity(new LazySectorZone(STREAK_SPEED, STREAK_SPEED_V)));
		addInitializer(new CollisionRadius(STREAK_RADIUS));
		addInitializer(new Mass(new UniformRandom(STREAK_MASS, 0)));
	}
}

//------------------------------------------------------------------------------------------------

class FireworksSpawn extends Spawn {
	
	public function FireworksSpawn(count:Random) {
		super(count);
		
		addInitializer(new Mask(FIREWORKS_MASK | COMMON_MASK));
		
		addInitializer(new DisplayObjectClass(ColorFader, [FIREWORKS_COLOR_1, FIREWORKS_COLOR_2]));
		addInitializer(new Life(new UniformRandom(FIREWORKS_LIFE, FIREWORKS_LIFE_V)));
		addInitializer(new Velocity(new LazySectorZone(FIREWORKS_SPEED, FIREWORKS_SPEED_V)));
		addInitializer(new CollisionRadius(FIREWORKS_RADIUS));
		addInitializer(new Mass(new UniformRandom(FIREWORKS_MASS, 0)));
	}
}

//------------------------------------------------------------------------------------------------

class SparkSpawn extends Spawn {
	
	public function SparkSpawn(count:Random) {
		super(count);
		
		addInitializer(new Mask(SPARK_MASK | COMMON_MASK));
		
		addInitializer(new DisplayObjectClass(ColorFader, [SPARK_COLOR_1, SPARK_COLOR_2]));
		addInitializer(new Life(new UniformRandom(SPARK_LIFE, SPARK_LIFE_V)));
		addInitializer(new Velocity(new LazySectorZone(SPARK_SPEED, SPARK_SPEED_V)));
		addInitializer(new CollisionRadius(SPARK_RADIUS));
		addInitializer(new Mass(new UniformRandom(SPARK_MASS, 0)));
	}
}

//------------------------------------------------------------------------------------------------

class CommonActions extends CompositeAction {
	
	public function CommonActions() {
		mask = COMMON_MASK;
		
		var gravity:Gravity = new Gravity();
		var field:UniformField = new UniformField(0, 0.025);
		field.massless = false;
		gravity.addField(field);
		
		addAction(new Age());
		addAction(new DeathLife());
		addAction(new Move());
		addAction(new StardustSpriteUpdate());
		addAction(gravity);
	}
}

//------------------------------------------------------------------------------------------------

class SeedActions extends CompositeAction {
	
	public function SeedActions() {
		mask = SEED_MASK;
		priority = -2;
		
		var trigger:DeathTrigger = new DeathTrigger();
		trigger.addAction(new FireworksSpawn(new UniformRandom(FIREWORKS_COUNT, 0)));
		trigger.addAction(new StreakSpawn(new UniformRandom(STREAK_COUNT, 0)));
		
		addAction(new ScaleCurve(SEED_LIFE * 0.5, 0));
		addAction(trigger);
	}
}

//------------------------------------------------------------------------------------------------

class StreakActions extends CompositeAction {
	
	public function StreakActions() {
		mask = STREAK_MASK;
		priority = -1;
		
		addAction(new ScaleCurve(0.5 * STREAK_LIFE, 0.5 * STREAK_LIFE));
		addAction(new AlphaCurve(0, 0.25 * STREAK_LIFE));
	}
}

//------------------------------------------------------------------------------------------------

class FireworksActions extends CompositeAction {
	
	public function FireworksActions() {
		mask = FIREWORKS_MASK;
		priority = -1;
		
		var trigger:DeathTrigger = new DeathTrigger();
		trigger.addAction(new SparkSpawn(new UniformRandom(SPARK_COUNT, 0)));
		
		var sc:ScaleCurve = new ScaleCurve(SPARK_LIFE, 0);
		sc.inScale = 0.25;
		
		addAction(sc);
		addAction(new Damping(FIREWORKS_DAMPING));
		addAction(trigger);
	}
}

//------------------------------------------------------------------------------------------------

class SparkActions extends CompositeAction {
	
	public function SparkActions() {
		mask = SPARK_MASK;
		
		var sc:ScaleCurve = new ScaleCurve(SPARK_LIFE, 0);
		sc.inScale = 0.25;
		
		addAction(sc);
		addAction(new Damping(SPARK_DAMPING));
	}
}

//------------------------------------------------------------------------------------------------

class LineRenderer extends Renderer {
	
	private var shape:Shape = new Shape();
	private var target:BitmapData;
	
	public function LineRenderer(target:BitmapData) {
		this.target = target;
	}
	
	override protected function render(e:EmitterEvent):void {
		if (!target) return;
		
		var md:MotionData2D;
		
		target.lock();
		for each (var p:Particle2D in e.particles) {
			if (!p.dictionary[this]) {
				md = new MotionData2D(p.x, p.y);
				p.dictionary[this] = md;
				
				continue;
			}
			
			md = p.dictionary[this] as MotionData2D;
			shape.graphics.clear();
			shape.graphics.lineStyle(p.scale * p.collisionRadius * 2, p.color, p.alpha);
			shape.graphics.moveTo(md.x, md.y);
			shape.graphics.lineTo(p.x, p.y);
			target.draw(shape);
			md.x = p.x;
			md.y = p.y;
		}
		target.unlock();
	}
}

//------------------------------------------------------------------------------------------------

class ColorFader extends StardustSprite {
	
	private var INIT_R		:Number;
	private var INIT_G		:Number;
	private var INIT_B		:Number;
	private var FINAL_R		:Number;
	private var FINAL_G		:Number;
	private var FINAL_B		:Number;
	
	public function ColorFader(color1:uint, color2:uint) {
		
		INIT_R  = (color1 & 0xFF0000) >> 16;
		INIT_G  = (color1 & 0x00FF00) >> 8;
		INIT_B  = (color1 & 0x0000FF);
		FINAL_R = (color2 & 0xFF0000) >> 16;
		FINAL_G = (color2 & 0x00FF00) >> 8;
		FINAL_B = (color2 & 0x0000FF);
	}
	
	//this class only modifies the particle color, not displaying anything
	override public function update(emitter:Emitter, particle:Particle, time:Number):void {
		var t:Number = particle.initLife - particle.life;
		var r:Number = StardustMath.interpolate(0, INIT_R, particle.initLife, FINAL_R, t);
		var g:Number = StardustMath.interpolate(0, INIT_G, particle.initLife, FINAL_G, t);
		var b:Number = StardustMath.interpolate(0, INIT_B, particle.initLife, FINAL_B, t);
		
		particle.color = (r << 16) | (g << 8) | b;
	}
}
